const std = @import("std");
const os = @import("../os.zig");
const console = @import("console.zig");
const driver = @import("../driver.zig");
const backspace = 0x08;

comptime {
    driver.register(&init, "ns16550a");
}

/// printing a panic message
pub var panicking: bool = false;
/// freeze uart output from other CPUs if panic has occurred
pub var freeze: bool = false;

// for transmission.
var tx_lock: os.Lock.Spin = .init("uart");
var tx_busy: bool = false; // is the UART busy sending?

var reg: os.lib.Mmio(ns16550, true) = .empty;

pub fn init(arg: ?*const anyopaque) void {
    const node: *const os.fdt.Node = @ptrCast(@alignCast(arg orelse return));
    const base = os.fdt.base(node);
    reg = .init(base);

    // disable interrupts.
    reg.write("ier", .{ .tx_enable = false, .rx_enable = false });

    // special mode to set baud rate.
    reg.write("lcr", .{ .baud_latch = true });

    // LSB for baud rate of 38.4K.
    reg.write("dll", 0x03);
    // MSB for baud rate of 38.4K.
    reg.write("dlm", 0x00);

    // leave set-baud mode,
    // and set word length to 8 bits, no parity.
    reg.write("lcr", .{ .word = .@"8bits" });

    // reset and enable fifo
    reg.write("fcr", .{ .enable = true, .clear_rx = true, .clear_tx = true });

    // enable transmit and receive interrupts.
    reg.write("ier", .{ .tx_enable = true, .rx_enable = true });
}

// Write a byte to UART with backspace/newline handling Used by kernel print() and character echoing
pub fn putc(char: u8) void {
    const cpu: ?*os.proc.Cpu = if (!panicking) .push() else null;
    defer if (!panicking) cpu.?.pop();

    while (freeze) {}

    if (char == backspace) {
        // if the user typed backspace, overwrite with a space.
        transmit(backspace); // \b
        transmit(' ');
        transmit(backspace); // \b
    } else if (char == '\n') {
        transmit('\r');
        transmit(char);
    } else {
        transmit(char);
    }
}

// Raw character transmission to UART (spins until ready)
fn transmit(char: u8) void {
    // wait for Transmit Holding Empty to be set in LSR.
    while (!reg.isSet("lsr", .{ .thr_empty = true })) {}
    reg.write("thr", char);
}

/// read one input character from the UART. return null if none is waiting.
fn receive() ?u8 {
    return if (reg.isSet("lsr", .{ .data_ready = true })) reg.read("rhr") else null;
}

/// handle a uart interrupt, raised because input has arrived, or the uart is ready for more output, or both.
pub fn intr() void {
    // acknowledge the interrupt
    _ = reg.read("isr");

    {
        tx_lock.acquire();
        defer tx_lock.release();
        if (reg.isSet("lsr", .{ .thr_empty = true })) {
            tx_busy = false;
            os.proc.wakeup(&tx_busy);
        }
    }

    // read and process incoming characters
    while (true) {
        if (receive()) |char| {
            console.intr(char);
        } else {
            break;
        }
    }
}

// transmit bytes to the uart. it blocks if the uart is busy, so it cannot be called from interrupts,
// only from write() system calls.
pub fn write(bytes: []const u8) !void {
    const p = os.proc.current().?;
    tx_lock.acquire();
    defer tx_lock.release();

    for (bytes) |char| {
        // wait for a UART transmit-complete interrupt to set tx_busy to false.
        while (tx_busy) {
            if (p.isKilled()) return error.ProcessNotFound;
            p.sleep(&tx_busy, &tx_lock);
        }

        reg.write("thr", char);
        tx_busy = true;
    }
}

/// the UART control registers. some have different meanings for read vs write. see
/// http://byterunner.com/16550.html
pub const ns16550 = enum(u3) {
    pub const Int = u8;

    /// Receiver Holding Register (R/W)
    rhr = 0,
    /// Interrupt Enable Register (R/W)
    ier = 1,
    /// Interrupt Status Register (R)
    isr = 2,
    /// Line Control Register (R/W)
    lcr = 3,
    /// Modem Control Register (R/W)
    mcr = 4,
    /// Line Status Register (R)
    lsr = 5,
    /// Modem Status Register (R)
    msr = 6,
    /// Scratch Pad Register (R/W)
    spr = 7,

    /// Transmitter Holding Register (R/W)
    pub const thr: @This() = .rhr;

    /// FIFO Control Register (W)
    pub const fcr: @This() = .isr;

    /// divisor latch least
    pub const dll: @This() = .rhr;
    /// divisor latch most
    pub const dlm: @This() = .ier;

    pub const Ier = packed struct(Int) {
        rx_enable: bool = false,
        tx_enable: bool = false,
        rx_line_status: bool = false,
        modem_status: bool = false,
        @"4-7": u4 = 0,
    };

    pub const Lcr = packed struct(Int) {
        word: enum(u2) {
            @"5bits" = 0b00,
            @"6bits" = 0b01,
            @"7bits" = 0b10,
            @"8bits" = 0b11,
        } = .@"5bits",
        stop: u1 = 0,
        parity_enable: bool = false,
        parity: enum(u1) { odd = 0, even = 1 } = .odd,
        force_parity: bool = false,
        break_control: bool = false,
        baud_latch: bool = false,
    };

    pub const Lsr = packed struct(Int) {
        data_ready: bool = false,
        overrun_error: bool = false,
        parity_error: bool = false,
        framing_error: bool = false,
        break_interrupt: bool = false,
        thr_empty: bool = true,
        transmitter_empty: bool = true,
        fifo_data_error: bool = false,
    };

    pub const Fcr = packed struct(Int) {
        enable: bool = false,
        clear_rx: bool = false,
        clear_tx: bool = false,
        mode: u1 = 0,
        @"4-5": u2 = 0,
        trigger_level: u2 = 0,
    };
};
